From f4efcf30d5595f0175bb3a1aba130502ab15a3ff Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 8 Aug 2025 17:35:35 +0800 Subject: [PATCH 01/11] add Normalizer --- src/Casts/Normalizer/ArrayNormalizerCast.php | 22 ++++++++ src/Casts/Normalizer/JsonNormalizerCast.php | 28 ++++++++++ .../Normalizer/NormalizerCastInterface.php | 10 ++++ .../Casts/NormalizerCastResolver.php | 25 +++++++++ src/SerializeContainer.php | 7 +++ src/Support/Config/ConfigManager.php | 31 +++++++++++ src/Support/Context/SerializeContext.php | 4 ++ src/Support/Factories/ContextFactory.php | 1 + .../From/NormalizerFromSerializeTest.php | 55 +++++++++++++++++++ 9 files changed, 183 insertions(+) create mode 100644 src/Casts/Normalizer/ArrayNormalizerCast.php create mode 100644 src/Casts/Normalizer/JsonNormalizerCast.php create mode 100644 src/Contracts/Normalizer/NormalizerCastInterface.php create mode 100644 src/Resolvers/Casts/NormalizerCastResolver.php create mode 100644 tests/Serialize/From/NormalizerFromSerializeTest.php diff --git a/src/Casts/Normalizer/ArrayNormalizerCast.php b/src/Casts/Normalizer/ArrayNormalizerCast.php new file mode 100644 index 0000000..c6527a5 --- /dev/null +++ b/src/Casts/Normalizer/ArrayNormalizerCast.php @@ -0,0 +1,22 @@ +match($values)){ + return $values->toArray(); + } + + return $values; + } +} \ No newline at end of file diff --git a/src/Casts/Normalizer/JsonNormalizerCast.php b/src/Casts/Normalizer/JsonNormalizerCast.php new file mode 100644 index 0000000..1bf6fe0 --- /dev/null +++ b/src/Casts/Normalizer/JsonNormalizerCast.php @@ -0,0 +1,28 @@ +match($values)){ + try { + $decoded = json_decode($values, true, 512, JSON_THROW_ON_ERROR); + return is_array($decoded) ? $decoded : $values; + } catch (JsonException $e) { + + } + } + + return $values; + } +} \ No newline at end of file diff --git a/src/Contracts/Normalizer/NormalizerCastInterface.php b/src/Contracts/Normalizer/NormalizerCastInterface.php new file mode 100644 index 0000000..09d49fa --- /dev/null +++ b/src/Contracts/Normalizer/NormalizerCastInterface.php @@ -0,0 +1,10 @@ +configManager->getNormalizerCasts() as $cast) { + $values = $cast->resolve($values); + } + + return $values; + } +} \ No newline at end of file diff --git a/src/SerializeContainer.php b/src/SerializeContainer.php index a17ddc7..8715a71 100644 --- a/src/SerializeContainer.php +++ b/src/SerializeContainer.php @@ -8,6 +8,7 @@ use Astral\Serialize\Faker\Rule\FakerDefaultRules; use Astral\Serialize\Resolvers\Casts\DataCollectionCastResolver; use Astral\Serialize\Resolvers\Casts\InputValueCastResolver; +use Astral\Serialize\Resolvers\Casts\NormalizerCastResolver; use Astral\Serialize\Resolvers\Casts\OutputCastResolver; use Astral\Serialize\Resolvers\GroupResolver; use Astral\Serialize\Resolvers\InputResolver; @@ -41,6 +42,7 @@ class SerializeContainer protected ?GroupResolver $groupResolver = null; protected ?ReflectionClassInstanceManager $reflectionClassInstanceManager = null; protected ?SerializeInstanceManager $serializeInstanceManager = null; + protected ?NormalizerCastResolver $normalizerCastResolver = null; protected ?DataCollectionCastResolver $attributePropertyResolver = null; protected ?InputResolver $propertyInputValueResolver = null; protected ?OutputResolver $propertyToArrayResolver = null; @@ -119,6 +121,11 @@ public function propertyInputValueResolver(): InputResolver ); } + public function normalizerCastResolver(): NormalizerCastResolver + { + return $this->normalizerCastResolver ??= new NormalizerCastResolver(ConfigManager::getInstance()); + } + public function inputValueCastResolver(): InputValueCastResolver { return $this->inputValueCastResolver ??= new InputValueCastResolver(ConfigManager::getInstance()); diff --git a/src/Support/Config/ConfigManager.php b/src/Support/Config/ConfigManager.php index 90b72b7..4e8f8ca 100644 --- a/src/Support/Config/ConfigManager.php +++ b/src/Support/Config/ConfigManager.php @@ -7,12 +7,15 @@ use Astral\Serialize\Casts\InputValue\InputObjectBestMatchChildCast; use Astral\Serialize\Casts\InputValue\InputValueEnumCast; use Astral\Serialize\Casts\InputValue\InputValueNullCast; +use Astral\Serialize\Casts\Normalizer\ArrayNormalizerCast; +use Astral\Serialize\Casts\Normalizer\JsonNormalizerCast; use Astral\Serialize\Casts\OutValue\OutArrayChildCast; use Astral\Serialize\Casts\OutValue\OutValueEnumCast; use Astral\Serialize\Casts\OutValue\OutValueGetterCast; use Astral\Serialize\Contracts\Attribute\DataCollectionCastInterface; use Astral\Serialize\Contracts\Attribute\InputValueCastInterface; use Astral\Serialize\Contracts\Attribute\OutValueCastInterface; +use Astral\Serialize\Contracts\Normalizer\NormalizerCastInterface; use Astral\Serialize\Enums\CacheDriverEnum; use Astral\Serialize\Exceptions\NotFoundAttributePropertyResolver; use Astral\Serialize\Support\Caching\MemoryCache; @@ -40,6 +43,12 @@ class ConfigManager OutValueGetterCast::class, ]; + /** @var (NormalizerCastInterface|string)[] $normalizerCasts */ + private array $normalizerCasts = [ +// JsonNormalizerCast::class, + ArrayNormalizerCast::class, + ]; + /** @var CacheDriverEnum|class-string $cacheDriver */ private string|CacheDriverEnum $cacheDriver = MemoryCache::class; @@ -52,6 +61,10 @@ public function __construct() foreach ($this->outputValueCasts as $key => $cast) { $this->outputValueCasts[$key] = new $cast(); } + + foreach ($this->normalizerCasts as $key => $cast) { + $this->normalizerCasts[$key] = new $cast(); + } } public static function getInstance(): ConfigManager @@ -59,6 +72,19 @@ public static function getInstance(): ConfigManager return self::$instance ??= new self(); } + /** + * @throws NotFoundAttributePropertyResolver + */ + public function addNormalizerCasts(NormalizerCastInterface|string $resolverClass): static + { + if (is_string($resolverClass) && !is_subclass_of($resolverClass, NormalizerCastInterface::class)) { + throw new NotFoundAttributePropertyResolver('Resolver class must be an instance of NormalizerCastInterface'); + } + $this->attributePropertyResolver[] = (is_string($resolverClass) ? new $resolverClass() : $resolverClass); + + return $this; + } + /** * @throws NotFoundAttributePropertyResolver */ @@ -113,6 +139,11 @@ public function getOutValueCasts(): array return $this->outputValueCasts; } + public function getNormalizerCasts(): array + { + return $this->normalizerCasts; + } + public function getCacheDriver(): string { if ($this->cacheDriver instanceof CacheDriverEnum) { diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index 679a861..542ec30 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -5,6 +5,7 @@ use Astral\Serialize\Exceptions\NotFoundGroupException; use Astral\Serialize\Faker\FakerResolver; use Astral\Serialize\Resolvers\Casts\DataCollectionCastResolver; +use Astral\Serialize\Resolvers\Casts\NormalizerCastResolver; use Astral\Serialize\Resolvers\GroupResolver; use Astral\Serialize\Resolvers\InputResolver; use Astral\Serialize\Resolvers\OutputResolver; @@ -38,6 +39,7 @@ public function __construct( private readonly InputResolver $propertyInputValueResolver, private readonly OutputResolver $propertyToArrayResolver, private readonly FakerResolver $fakerResolver, + private readonly NormalizerCastResolver $normalizerCastResolver, ) { } @@ -187,8 +189,10 @@ className: $type->className, */ public function from(mixed ...$payload): object { + $payloads = []; foreach ($payload as $field => $itemPayload) { + $itemPayload = $this->normalizerCastResolver->resolve($itemPayload); $values = is_numeric($field) && is_array($itemPayload) ? $itemPayload : [$field => $itemPayload]; $payloads = [...$payloads, ...$values]; } diff --git a/src/Support/Factories/ContextFactory.php b/src/Support/Factories/ContextFactory.php index 9b20d6a..4661f00 100644 --- a/src/Support/Factories/ContextFactory.php +++ b/src/Support/Factories/ContextFactory.php @@ -28,6 +28,7 @@ public static function build(string $className): SerializeContext propertyInputValueResolver: SerializeContainer::get()->propertyInputValueResolver(), propertyToArrayResolver: SerializeContainer::get()->propertyToArrayResolver(), fakerResolver: SerializeContainer::get()->fakerResolver(), + normalizerCastResolver: SerializeContainer::get()->normalizerCastResolver(), )); } } diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php new file mode 100644 index 0000000..af69d28 --- /dev/null +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -0,0 +1,55 @@ +name_one = 'one name'; + $normalizerOne->id_one = 1; + + $normalizerTwo = new NormalizerTwo(); + $normalizerTwo->name_two = 'two name'; + $normalizerTwo->id_two = 2; + + $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); + + expect($res->one)->toBeInstanceOf(NormalizerOne::class) + ->and($res->one->name_one)->toBe('one name') + ->and($res->one->id_one)->toBe(1) + ->and($res->two)->toBeInstanceOf(NormalizerTwo::class) + ->and($res->two->name_two)->toBe('two name') + ->and($res->two->id_two)->toBe(2) + ->and($res->three)->toBeArray() + ->and($res->three)->toMatchArray([ + 'name_one' => 'one name', + 'id_one' => 1 + ]); + + $resJson = json_encode($res); + expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); +}); + From e9c0ea892eb910f788e7841cffe6ffbf1931a023 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Thu, 21 Aug 2025 16:36:28 +0800 Subject: [PATCH 02/11] add Normalizer --- src/Serialize.php | 30 ++++++++++++++++--- src/Support/Context/SerializeContext.php | 22 ++++++++++++++ .../From/NormalizerFromSerializeTest.php | 25 +++++++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/Serialize.php b/src/Serialize.php index 04faf3b..15d9fd2 100644 --- a/src/Serialize.php +++ b/src/Serialize.php @@ -7,6 +7,14 @@ use Astral\Serialize\Support\Factories\ContextFactory; use JsonSerializable; +/** + * @method void withResponses(array $responses) static + * @see SerializeContext::withResponses() + * @method void setCode(string|int $code) static + * @see SerializeContext::setCode() + * @method void setMessage(string $message) static + * @see SerializeContext::setMessage() + */ abstract class Serialize implements JsonSerializable { private ?SerializeContext $_context = null; @@ -76,14 +84,17 @@ public function __debugInfo() public function jsonSerialize(): array { - $baseResponse = Config::get('response',[]); - if($baseResponse){ + $baseResponses = Config::get('response',[]); + $customerResponses = $this->getContext()?->getResponses() ?? []; + $responses = array_merge($baseResponses, $customerResponses); + + if($responses){ $resultData = []; - foreach ($baseResponse as $field => $item){ + foreach ($responses as $field => $item){ if($item === 'T'){ $resultData[$field] = $this->toArray(); }else{ - $resultData[$field] = $item['example'] ?? ''; + $resultData[$field] = $item['value'] ?? ($item['example'] ?? ''); } } return $resultData; @@ -91,4 +102,15 @@ public function jsonSerialize(): array return $this->toArray(); } + + public function __call(string $name, array $arguments) + { + if ($this->getContext() === null) { + $this->setContext(ContextFactory::build(static::class)); + } + + $this->getContext()->{$name}(...$arguments); + + return $this; + } } diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index 542ec30..ec69d62 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -26,6 +26,7 @@ class SerializeContext { private array $groups = []; + private array $responses = []; public function __construct( /** @var class-string */ @@ -44,6 +45,27 @@ public function __construct( } + public function setCode(string|int $code, $description ='' , $field = 'code'): void + { + $this->responses[$field] = ['description' => $description,'value' => $code]; + } + + public function setMessage(string $message, $description ='' , $field = 'message'): void + { + $this->responses[$field] = ['description' => $description,'value' => $message]; + } + + public function withResponses(array $responses): self + { + $this->responses = $responses; + return $this; + } + + public function getResponses(): array + { + return $this->responses ?? []; + } + public function getChooseSerializeContext(): ChooseSerializeContext { return $this->chooseSerializeContext; diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index af69d28..c416211 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -19,7 +19,6 @@ class NormalizerClass extends Serialize { public NormalizerOne $one; public NormalizerTwo $two; - public mixed $three; } @@ -49,7 +48,31 @@ class NormalizerClass extends Serialize 'id_one' => 1 ]); + +}); + +it('test json_encode Serialize class', function () { + + $normalizerOne = new NormalizerOne(); + $normalizerOne->name_one = 'one name'; + $normalizerOne->id_one = 1; + + $normalizerTwo = new NormalizerTwo(); + $normalizerTwo->name_two = 'two name'; + $normalizerTwo->id_two = 2; + + $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); $resJson = json_encode($res); expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + + $res->setMessage('233'); + $resJson = json_encode($res); + expect($resJson)->toBe('{"code":200,"message":"233","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + + $res->setCode(-1); + $resJson = json_encode($res); + expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + + }); From 844457b99b6b8f10bcf6ac52f70153c3b913260c Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 15:13:11 +0800 Subject: [PATCH 03/11] add Normalizer --- src/Serialize.php | 19 +++++++++++++++++-- src/Support/Context/SerializeContext.php | 14 ++++++++++++++ .../From/NormalizerFromSerializeTest.php | 11 ++++++++--- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Serialize.php b/src/Serialize.php index 15d9fd2..09d792c 100644 --- a/src/Serialize.php +++ b/src/Serialize.php @@ -43,6 +43,20 @@ public function withGroups(array|string $groups): static return $this; } + public function toJsonString(): bool|string + { + return json_encode($this); + } + + public function withoutResponseToJsonString(): string + { + if ($this->getContext() === null) { + $this->setContext(ContextFactory::build(static::class)); + } + + return json_encode($this->getContext()->toArrayWithoutResponse($this->toArray())); + } + public function toArray(): array { if ($this->getContext() === null) { @@ -74,6 +88,7 @@ public static function faker(): static return $instance; } + public function __debugInfo() { $res = get_object_vars($this); @@ -92,7 +107,7 @@ public function jsonSerialize(): array $resultData = []; foreach ($responses as $field => $item){ if($item === 'T'){ - $resultData[$field] = $this->toArray(); + $resultData[$field] = $this->getContext()->toArrayWithoutResponse($this->toArray()); }else{ $resultData[$field] = $item['value'] ?? ($item['example'] ?? ''); } @@ -100,7 +115,7 @@ public function jsonSerialize(): array return $resultData; } - return $this->toArray(); + return $this->getContext()->toArrayWithoutResponse($this->toArray()); } public function __call(string $name, array $arguments) diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index ec69d62..a8f2821 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -246,4 +246,18 @@ public function toArray(object $object): array $this->chooseSerializeContext->setGroups($this->getGroups()); return $this->propertyToArrayResolver->resolve($this->chooseSerializeContext, $this->getGroupCollection(), $object); } + + public function toArrayWithoutResponse($data) + { + if ($data instanceof Serialize) { + return $data->toArray(); + } elseif (is_array($data)) { + foreach ($data as $key => $value) { + $data[$key] = $this->toArrayWithoutResponse($value); + } + return $data; + } + return $data; + } + } diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index c416211..488be3f 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -63,16 +63,21 @@ class NormalizerClass extends Serialize $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); $res->setMessage('233'); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":200,"message":"233","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":200,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); $res->setCode(-1); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_one":"one name","id_one":1}},"two":{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"name_two":"two name","id_two":2}},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); + $resJson = $res->withoutResponseToJsonString(); + expect($resJson)->toBe('{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}'); + + $resJson = $res->toJsonString(); + expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); }); From c707aae28b17bd45f43fccbde73d87f4dbe12da8 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 16:27:37 +0800 Subject: [PATCH 04/11] add Normalizer --- src/Support/Context/SerializeContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index a8f2821..b55a33b 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -250,7 +250,7 @@ public function toArray(object $object): array public function toArrayWithoutResponse($data) { if ($data instanceof Serialize) { - return $data->toArray(); + return get_object_vars($data); } elseif (is_array($data)) { foreach ($data as $key => $value) { $data[$key] = $this->toArrayWithoutResponse($value); From b3eab7ec5e6ddbfabe101fa352cdaf47ddd4e11c Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 17:35:23 +0800 Subject: [PATCH 05/11] fixed docs --- docs/en/annotation/alisa-annotation.md | 5 ++--- docs/en/faker/collection-faker.md | 4 ++-- docs/en/mapper/array-mapper.md | 16 ++++++++-------- docs/zh/annotation/alisa-annotation.md | 3 +-- docs/zh/faker/collection-faker.md | 4 ++-- docs/zh/mapper/array-mapper.md | 14 +++++++------- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/en/annotation/alisa-annotation.md b/docs/en/annotation/alisa-annotation.md index 7be00ae..cba8605 100644 --- a/docs/en/annotation/alisa-annotation.md +++ b/docs/en/annotation/alisa-annotation.md @@ -128,9 +128,8 @@ $complexUser = ComplexUser::from([ 'user_tags' => ['developer', 'programmer'] ]); -// Convert to standard array -$complexUserArray = $complexUser->toArray(); -// $complexUserArray toArray: +var_dump($complexUserListFaker) +// Content of $complexUserListFaker: // [ // 'profile' => UserProfile Object ([ // 'nickname' => 'job', diff --git a/docs/en/faker/collection-faker.md b/docs/en/faker/collection-faker.md index be9a1f7..4d9269b 100644 --- a/docs/en/faker/collection-faker.md +++ b/docs/en/faker/collection-faker.md @@ -21,8 +21,8 @@ $userList = UserListFaker::faker(); $complexUserListFaker = UserListFaker::faker(); -$complexUserListFakerArray = $complexUserListFaker->toArray(); -// Content of $complexUserListFakerArray: +var_dump($complexUserListFaker) +// Content of $complexUserListFaker: // [ // 'profile' => [ // [0] => UserProfile Object ( diff --git a/docs/en/mapper/array-mapper.md b/docs/en/mapper/array-mapper.md index f836f7f..ff63f73 100644 --- a/docs/en/mapper/array-mapper.md +++ b/docs/en/mapper/array-mapper.md @@ -40,8 +40,8 @@ $data1 = MultiArraySerialize::from( ] ); -$data1Array = $data1->toArray(); -// Content of $data1Array: +var_dump($data1) +// Content of $data1: // [ // 'mixedTypeArray' => [ // [0] => ArrayOne Object @@ -72,8 +72,8 @@ $data2 = MultiArraySerialize::from( ] ); -$data2Array = $data2->toArray(); -// Content of $data2Array: +var_dump($data2) +// Content of $data2: // [ // 'multiTypeArray' => [ // ArrayOne Object ( @@ -97,8 +97,8 @@ $data3 = MultiArraySerialize::from( ] ); -$data3Array = $data3->toArray(); -// $data3Array toArray: +var_dump($data3) +// Content of $data3: // [ // 'keyValueMixedArray' => [ // 'user1' => ArrayOne Object ( @@ -121,8 +121,8 @@ $data4 = MultiArraySerialize::from( ] ); -$data4Array = $data4->toArray(); -// $data4Array toArray: +var_dump($data4) +// Content of $data4: // [ // 'mixedTypeArray' => [ // ['unknown' => 'data1'], diff --git a/docs/zh/annotation/alisa-annotation.md b/docs/zh/annotation/alisa-annotation.md index ff61fb7..77ad700 100644 --- a/docs/zh/annotation/alisa-annotation.md +++ b/docs/zh/annotation/alisa-annotation.md @@ -128,8 +128,7 @@ $complexUser = ComplexUser::from([ 'user_tags' => ['developer', 'programmer'] ]); -// 转换为标准数组 -$complexUserArray = $complexUser->toArray(); +var_dump($complexUser); // $complexUserArray 的内容: // [ // 'profile' => UserProfile Object ([ diff --git a/docs/zh/faker/collection-faker.md b/docs/zh/faker/collection-faker.md index a7b2126..7208ccd 100644 --- a/docs/zh/faker/collection-faker.md +++ b/docs/zh/faker/collection-faker.md @@ -21,8 +21,8 @@ $userList = UserListFaker::faker(); $complexUserListFaker = UserListFaker::faker(); -$complexUserListFakerArray = $complexUserListFaker->toArray(); -// $complexUserListFakerArray 的内容: +var_dump($complexUserListFaker) +// $complexUserListFaker 的内容: // [ // 'profile' => [ // [0] => UserProfile Object ( diff --git a/docs/zh/mapper/array-mapper.md b/docs/zh/mapper/array-mapper.md index 17434cc..5ecc9e3 100644 --- a/docs/zh/mapper/array-mapper.md +++ b/docs/zh/mapper/array-mapper.md @@ -40,8 +40,8 @@ $data1 = MultiArraySerialize::from( ] ); -$data1Array = $data1->toArray(); -// $data1Array 的内容: +var_dump($data1) +// $data1 的内容: // [ // 'mixedTypeArray' => [ // [0] => ArrayOne Object @@ -72,8 +72,8 @@ $data2 = MultiArraySerialize::from( ] ); -$data2Array = $data2->toArray(); -// $data2Array 的内容: +var_dump($data2) +// $data2 的内容: // [ // 'multiTypeArray' => [ // ArrayOne Object ( @@ -97,8 +97,8 @@ $data3 = MultiArraySerialize::from( ] ); -$data3Array = $data3->toArray(); -// $data3Array 的内容: +var_dump($data3) +// $data3 的内容: // [ // 'keyValueMixedArray' => [ // 'user1' => ArrayOne Object ( @@ -121,7 +121,7 @@ $data4 = MultiArraySerialize::from( ] ); -$data4Array = $data4->toArray(); +var_dump($data4) // $data4Array 的内容: // [ // 'mixedTypeArray' => [ From 32a3fa26351aa34d218379c407aec4b07d6970d8 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 17:35:35 +0800 Subject: [PATCH 06/11] fixed docs --- docs/en/mapper/union-mapper.md | 12 ++++++------ docs/zh/mapper/union-mapper.md | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/en/mapper/union-mapper.md b/docs/en/mapper/union-mapper.md index b9f538c..e4bbfde 100644 --- a/docs/en/mapper/union-mapper.md +++ b/docs/en/mapper/union-mapper.md @@ -60,8 +60,8 @@ $data2 = FlexibleData::from([ echo $data2->userIdentifier; // Output User object echo $data2->complexIdentifier; // Output AdminUser object -$data2Array = $data2->toArray(); -// Content of $data2Array: +var_dump($data2) +// Content of $data2: // [ // 'flexibleId' => 'ABC123', // 'userIdentifier' => User Object ( @@ -87,11 +87,11 @@ $data3 = FlexibleData::from([ ] ]); -echo $data2->userIdentifier; // Output User object -echo $data2->complexIdentifier; // Output AdminUser object +echo $data3->userIdentifier; // Output User object +echo $data3->complexIdentifier; // Output AdminUser object -$data3Array = $data3->toArray(); -// Content of $data3Array: +var_dump($data3) +// Content of $data3: // [ // 'flexibleId' => 'USER001', // 'userIdentifier' => User Object ( diff --git a/docs/zh/mapper/union-mapper.md b/docs/zh/mapper/union-mapper.md index 08d838e..0e38e9b 100644 --- a/docs/zh/mapper/union-mapper.md +++ b/docs/zh/mapper/union-mapper.md @@ -60,7 +60,7 @@ $data2 = FlexibleData::from([ echo $data2->userIdentifier; // 输出 User 对象 echo $data2->complexIdentifier; // 输出 User 对象 -$data2Array = $data2->toArray(); +var_dump($data2) // $data2Array 的内容: // [ // 'flexibleId' => 'ABC123', @@ -87,10 +87,10 @@ $data3 = FlexibleData::from([ ] ]); -echo $data2->userIdentifier; // 输出 User 对象 -echo $data2->complexIdentifier; // 输出 AdminUser 对象 +echo $data3->userIdentifier; // 输出 User 对象 +echo $data3->complexIdentifier; // 输出 AdminUser 对象 -$data3Array = $data3->toArray(); +var_dump($data3) // $data3Array 的内容: // [ // 'flexibleId' => 'USER001', From 4c5c156adab2b5a513e726a983769c223a2cd008 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 17:36:08 +0800 Subject: [PATCH 07/11] fixed toArray Bug --- src/Resolvers/OutputResolver.php | 6 +++--- src/Support/Config/ConfigManager.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Resolvers/OutputResolver.php b/src/Resolvers/OutputResolver.php index 01a9014..d8df062 100644 --- a/src/Resolvers/OutputResolver.php +++ b/src/Resolvers/OutputResolver.php @@ -3,6 +3,7 @@ namespace Astral\Serialize\Resolvers; use Astral\Serialize\Resolvers\Casts\OutputCastResolver; +use Astral\Serialize\Serialize; use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Collections\GroupDataCollection; use Astral\Serialize\Support\Context\ChoosePropertyContext; @@ -22,7 +23,6 @@ public function __construct( public function resolve(ChooseSerializeContext $chooseContext, GroupDataCollection $groupCollection, object $object): array { - $context = new OutContext( className: $groupCollection->getClassName(), classInstance: $object, @@ -32,7 +32,6 @@ classInstance: $object, $properties = $groupCollection->getProperties(); - $toArray = []; foreach ($properties as $collection) { @@ -53,8 +52,9 @@ classInstance: $object, ); foreach ($matchData['names'] as $name) { - $toArray[$name] = $resolvedValue; + $toArray[$name] = $resolvedValue instanceof Serialize ? $resolvedValue->toArray() : $resolvedValue; } + } return $toArray; diff --git a/src/Support/Config/ConfigManager.php b/src/Support/Config/ConfigManager.php index 4e8f8ca..20db6a3 100644 --- a/src/Support/Config/ConfigManager.php +++ b/src/Support/Config/ConfigManager.php @@ -38,7 +38,7 @@ class ConfigManager /** @var (OutValueCastInterface|string)[] $outputValueCasts */ private array $outputValueCasts = [ - OutArrayChildCast::class, +// OutArrayChildCast::class, OutValueEnumCast::class, OutValueGetterCast::class, ]; From 356a1ef965d96c5f330f6942a2ff976274f0dcd9 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Fri, 29 Aug 2025 17:36:35 +0800 Subject: [PATCH 08/11] added JsonSerializable --- src/Serialize.php | 10 +++------ src/Support/Context/SerializeContext.php | 14 ------------ .../From/NormalizerFromSerializeTest.php | 22 ++++++++++++------- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/Serialize.php b/src/Serialize.php index 09d792c..11e4f9e 100644 --- a/src/Serialize.php +++ b/src/Serialize.php @@ -50,11 +50,7 @@ public function toJsonString(): bool|string public function withoutResponseToJsonString(): string { - if ($this->getContext() === null) { - $this->setContext(ContextFactory::build(static::class)); - } - - return json_encode($this->getContext()->toArrayWithoutResponse($this->toArray())); + return json_encode($this->toArray()); } public function toArray(): array @@ -107,7 +103,7 @@ public function jsonSerialize(): array $resultData = []; foreach ($responses as $field => $item){ if($item === 'T'){ - $resultData[$field] = $this->getContext()->toArrayWithoutResponse($this->toArray()); + $resultData[$field] = $this->toArray(); }else{ $resultData[$field] = $item['value'] ?? ($item['example'] ?? ''); } @@ -115,7 +111,7 @@ public function jsonSerialize(): array return $resultData; } - return $this->getContext()->toArrayWithoutResponse($this->toArray()); + return $this->toArray(); } public function __call(string $name, array $arguments) diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index b55a33b..b601405 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -211,7 +211,6 @@ className: $type->className, */ public function from(mixed ...$payload): object { - $payloads = []; foreach ($payload as $field => $itemPayload) { $itemPayload = $this->normalizerCastResolver->resolve($itemPayload); @@ -247,17 +246,4 @@ public function toArray(object $object): array return $this->propertyToArrayResolver->resolve($this->chooseSerializeContext, $this->getGroupCollection(), $object); } - public function toArrayWithoutResponse($data) - { - if ($data instanceof Serialize) { - return get_object_vars($data); - } elseif (is_array($data)) { - foreach ($data as $key => $value) { - $data[$key] = $this->toArrayWithoutResponse($value); - } - return $data; - } - return $data; - } - } diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index 488be3f..9feac3a 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -1,5 +1,7 @@ 'one name', 'id_one' => 1 ]); - +// }); @@ -62,22 +67,23 @@ class NormalizerClass extends Serialize $normalizerTwo->id_two = 2; $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); + $resJson = json_encode($res); - expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); $res->setMessage('233'); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":200,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":200,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); $res->setCode(-1); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); $resJson = $res->withoutResponseToJsonString(); - expect($resJson)->toBe('{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}'); + var_dump($resJson); + expect($resJson)->toBe('{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}'); $resJson = $res->toJsonString(); - expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_two":2},"three":{"name_one":"one name","id_one":1}}}'); - -}); + expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); +}); \ No newline at end of file From 7438e8328c099bafe0745220fbe5d759e126ce3b Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Mon, 1 Sep 2025 11:34:44 +0800 Subject: [PATCH 09/11] add Normalizer fixed toArray --- src/Annotations/Input/InputDateFormat.php | 4 + src/Annotations/Output/OutputDateFormat.php | 7 +- src/Casts/Normalizer/ArrayNormalizerCast.php | 6 +- .../Normalizer/DateTimeNormalizerCast.php | 28 +++++ src/Casts/Normalizer/JsonNormalizerCast.php | 4 +- src/Casts/Normalizer/ObjectNormalizerCast.php | 22 ++++ .../Normalizer/NormalizerCastInterface.php | 2 +- src/Enums/ConfigCastEnum.php | 32 ++++++ src/OpenApi/Collections/OpenApiCollection.php | 29 +++-- src/OpenApi/Frankenphp/docs/index.php | 2 +- src/OpenApi/Frankenphp/index.php | 1 + src/OpenApi/Storage/OpenAPI/OpenAPI.php | 2 +- .../Storage/OpenAPI/ResponseStorage.php | 14 +-- ...er.php => InputNormalizerCastResolver.php} | 8 +- .../Casts/OutNormalizerCastResolver.php | 23 ++++ src/Resolvers/OutputResolver.php | 5 +- src/Serialize.php | 13 ++- src/SerializeContainer.php | 16 ++- src/Support/Config/ConfigManager.php | 100 ++++++++---------- src/Support/Context/SerializeContext.php | 17 ++- src/Support/Factories/ContextFactory.php | 6 +- tests/Openapi/BaseGroupOpenApiTest.php | 7 +- tests/Openapi/EnumOpenApiTest.php | 16 +-- tests/Openapi/OpenApiTest.php | 9 +- .../From/NormalizerFromSerializeTest.php | 22 ++-- .../OutDateFormatToArraySerializeTest.php | 6 +- 26 files changed, 248 insertions(+), 153 deletions(-) create mode 100644 src/Casts/Normalizer/DateTimeNormalizerCast.php create mode 100644 src/Casts/Normalizer/ObjectNormalizerCast.php create mode 100644 src/Enums/ConfigCastEnum.php rename src/Resolvers/Casts/{NormalizerCastResolver.php => InputNormalizerCastResolver.php} (62%) create mode 100644 src/Resolvers/Casts/OutNormalizerCastResolver.php diff --git a/src/Annotations/Input/InputDateFormat.php b/src/Annotations/Input/InputDateFormat.php index 41e4585..c46a284 100755 --- a/src/Annotations/Input/InputDateFormat.php +++ b/src/Annotations/Input/InputDateFormat.php @@ -8,6 +8,7 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\InputValueContext; use Attribute; +use DateInvalidTimeZoneException; use DateTime; use DateTimeInterface; use DateTimeZone; @@ -28,6 +29,9 @@ public function match(mixed $value, DataCollection $collection, InputValueContex return is_string($value) || is_numeric($value); } + /** + * @throws DateInvalidTimeZoneException + */ public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): string|DateTime { diff --git a/src/Annotations/Output/OutputDateFormat.php b/src/Annotations/Output/OutputDateFormat.php index efd51c2..f343e36 100755 --- a/src/Annotations/Output/OutputDateFormat.php +++ b/src/Annotations/Output/OutputDateFormat.php @@ -8,14 +8,12 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\OutContext; use Attribute; +use DateInvalidTimeZoneException; use DateTime; use DateTimeInterface; use DateTimeZone; use Exception; -/** - * toArray 输出值为 固定日期格式 默认 YYYY-MM-DD HH:ii:ss的日期格式 - */ #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] class OutputDateFormat implements OutValueCastInterface { @@ -35,6 +33,9 @@ public function resolve(mixed $value, DataCollection $collection, OutContext $co return $this->formatValue($value); } + /** + * @throws DateInvalidTimeZoneException + */ private function formatValue(mixed $value): ?string { $timezone = $this->timezone ? new DateTimeZone($this->timezone) : null; diff --git a/src/Casts/Normalizer/ArrayNormalizerCast.php b/src/Casts/Normalizer/ArrayNormalizerCast.php index c6527a5..46ef4d5 100644 --- a/src/Casts/Normalizer/ArrayNormalizerCast.php +++ b/src/Casts/Normalizer/ArrayNormalizerCast.php @@ -8,15 +8,15 @@ class ArrayNormalizerCast implements NormalizerCastInterface { public function match(mixed $values): bool { - return is_object($values) && method_exists($values, 'toArray'); + return is_object($values) && method_exists($values, 'toArray'); } public function resolve(mixed $values): mixed { - if($this->match($values)){ + if ($this->match($values)) { return $values->toArray(); } return $values; } -} \ No newline at end of file +} diff --git a/src/Casts/Normalizer/DateTimeNormalizerCast.php b/src/Casts/Normalizer/DateTimeNormalizerCast.php new file mode 100644 index 0000000..86bb566 --- /dev/null +++ b/src/Casts/Normalizer/DateTimeNormalizerCast.php @@ -0,0 +1,28 @@ +match($values)) { + try { + return $values->format('Y-m-d H:i:s'); + } catch (JsonException $e) { + return ''; + } + } + + return $values; + } +} diff --git a/src/Casts/Normalizer/JsonNormalizerCast.php b/src/Casts/Normalizer/JsonNormalizerCast.php index 1bf6fe0..4a6bd87 100644 --- a/src/Casts/Normalizer/JsonNormalizerCast.php +++ b/src/Casts/Normalizer/JsonNormalizerCast.php @@ -14,7 +14,7 @@ public function match(mixed $values): bool public function resolve(mixed $values): array { - if($this->match($values)){ + if ($this->match($values)) { try { $decoded = json_decode($values, true, 512, JSON_THROW_ON_ERROR); return is_array($decoded) ? $decoded : $values; @@ -25,4 +25,4 @@ public function resolve(mixed $values): array return $values; } -} \ No newline at end of file +} diff --git a/src/Casts/Normalizer/ObjectNormalizerCast.php b/src/Casts/Normalizer/ObjectNormalizerCast.php new file mode 100644 index 0000000..a8e9c12 --- /dev/null +++ b/src/Casts/Normalizer/ObjectNormalizerCast.php @@ -0,0 +1,22 @@ +match($values)) { + return (array)$values; + } + + return $values; + } +} diff --git a/src/Contracts/Normalizer/NormalizerCastInterface.php b/src/Contracts/Normalizer/NormalizerCastInterface.php index 09d49fa..eca8a02 100644 --- a/src/Contracts/Normalizer/NormalizerCastInterface.php +++ b/src/Contracts/Normalizer/NormalizerCastInterface.php @@ -7,4 +7,4 @@ interface NormalizerCastInterface public function match(mixed $values): bool; public function resolve(mixed $values): mixed; -} \ No newline at end of file +} diff --git a/src/Enums/ConfigCastEnum.php b/src/Enums/ConfigCastEnum.php new file mode 100644 index 0000000..e5b0c1f --- /dev/null +++ b/src/Enums/ConfigCastEnum.php @@ -0,0 +1,32 @@ + DataCollectionCastInterface::class, + self::OUTPUT_VALUE => OutValueCastInterface::class, + self::INPUT_VALUE => InputValueCastInterface::class, + self::INPUT_NORMALIZER, self::OUT_NORMALIZER => NormalizerCastInterface::class, + }; + } + + public static function getValues(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 33dc041..5bf428f 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -19,7 +19,6 @@ use Astral\Serialize\OpenApi\Storage\OpenAPI\SchemaStorage; use Astral\Serialize\Resolvers\GroupResolver; use Astral\Serialize\Serialize; -use Astral\Serialize\SerializeContainer; use Astral\Serialize\Support\Factories\ContextFactory; use Psr\SimpleCache\InvalidArgumentException; use ReflectionMethod; @@ -58,7 +57,7 @@ public function build(): Method $requestBody = $this->buildRequestBody( className:$this->getRequestBodyClass(), contentType:$this->requestBody->contentType ?? ContentTypeEnum::JSON, - groups: $this->requestBody->groups ?? [] + groups: $this->requestBody->groups ?? [] ); $response = $this->buildResponse( @@ -71,10 +70,9 @@ className:$this->getResponseClass(), return $openAPIMethod; } - public function getRequestBodyClass(): string { - if($this->requestBody?->className){ + if ($this->requestBody?->className) { return $this->requestBody->className; } @@ -88,12 +86,11 @@ public function getRequestBodyClass(): string return ''; } - - public function buildRequestBody(string $className,ContentTypeEnum $contentType,array $groups = []): RequestBodyStorage + public function buildRequestBody(string $className, ContentTypeEnum $contentType, array $groups = []): RequestBodyStorage { $openAPIRequestBody = new RequestBodyStorage($contentType); if (is_subclass_of($className, Serialize::class)) { - $schemaStorage = (new SchemaStorage())->build($this->buildRequestParameterCollections($className,$groups)); + $schemaStorage = (new SchemaStorage())->build($this->buildRequestParameterCollections($className, $groups)); $openAPIRequestBody->withParameter($schemaStorage); } @@ -102,7 +99,7 @@ public function buildRequestBody(string $className,ContentTypeEnum $contentType, public function getResponseClass(): string { - if($this->response?->className){ + if ($this->response?->className) { return $this->response->className; } @@ -118,18 +115,18 @@ public function getResponseClass(): string /** * @throws InvalidArgumentException */ - public function buildResponse(string $className,array $groups = []): ResponseStorage + public function buildResponse(string $className, array $groups = []): ResponseStorage { $responseStorage = new ResponseStorage(); - $baseResponse = Config::get('response',[]); + $baseResponse = Config::get('response', []); if ($className) { - $schemaStorage = (new SchemaStorage())->build($this->buildResponseParameterCollections($className,$groups)); + $schemaStorage = (new SchemaStorage())->build($this->buildResponseParameterCollections($className, $groups)); $responseStorage->withParameter($schemaStorage); } - if($baseResponse){ + if ($baseResponse) { $responseStorage->addGlobParameters($baseResponse); } @@ -148,13 +145,13 @@ public function buildRequestParameterCollections(string $className, array $group $serializeContext = ContextFactory::build($className); $serializeContext->setGroups($groups)->from(); $properties = $serializeContext->getGroupCollection()->getProperties(); - $groups = $groups ?: [$className]; + $groups = $groups ?: [$className]; $vols = []; foreach ($properties as $property) { - if($property->isInputIgnoreByGroups($groups) || !$this->groupResolver->resolveExistsGroupsByDataCollection($property, $groups, $className)){ + if ($property->isInputIgnoreByGroups($groups) || !$this->groupResolver->resolveExistsGroupsByDataCollection($property, $groups, $className)) { continue; } @@ -192,12 +189,12 @@ public function buildResponseParameterCollections(string $className, array $grou $serializeContext = ContextFactory::build($className); $serializeContext->from(); $properties = $serializeContext->getGroupCollection()->getProperties(); - $groups = $groups ?: [$className]; + $groups = $groups ?: [$className]; $vols = []; foreach ($properties as $property) { - if($property->isOutIgnoreByGroups($groups) || !$this->groupResolver->resolveExistsGroupsByDataCollection($property, $groups, $className)){ + if ($property->isOutIgnoreByGroups($groups) || !$this->groupResolver->resolveExistsGroupsByDataCollection($property, $groups, $className)) { continue; } diff --git a/src/OpenApi/Frankenphp/docs/index.php b/src/OpenApi/Frankenphp/docs/index.php index f93de6f..3d2ef0d 100644 --- a/src/OpenApi/Frankenphp/docs/index.php +++ b/src/OpenApi/Frankenphp/docs/index.php @@ -15,7 +15,7 @@ diff --git a/src/OpenApi/Frankenphp/index.php b/src/OpenApi/Frankenphp/index.php index eeed3d5..5dee947 100644 --- a/src/OpenApi/Frankenphp/index.php +++ b/src/OpenApi/Frankenphp/index.php @@ -1,4 +1,5 @@ tags[$tag->name] = $tag; + // $this->tags[$tag->name] = $tag; } public function withPaths(array $paths): self diff --git a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php index 10126c9..5ac25a6 100755 --- a/src/OpenApi/Storage/OpenAPI/ResponseStorage.php +++ b/src/OpenApi/Storage/OpenAPI/ResponseStorage.php @@ -25,19 +25,19 @@ public function withParameter(SchemaStorage $schema): static public function addGlobParameters(array $vols): void { - $dates = $this->parameter['properties'] ?? []; - $required = $this->parameter['required'] ?? []; + $dates = $this->parameter['properties'] ?? []; + $required = $this->parameter['required'] ?? []; $this->parameter['properties'] = []; - foreach ($vols as $field => $item){ - if($item === 'T'){ + foreach ($vols as $field => $item) { + if ($item === 'T') { $this->parameter['properties'][$field] = [ 'type' => 'object', - 'properties' => $dates, - 'required' => $required, + 'properties' => $dates, + 'required' => $required, ]; - }else{ + } else { $this->parameter['properties'][$field] = [ 'type' => 'string', 'description' => $item['description'] ?? '', diff --git a/src/Resolvers/Casts/NormalizerCastResolver.php b/src/Resolvers/Casts/InputNormalizerCastResolver.php similarity index 62% rename from src/Resolvers/Casts/NormalizerCastResolver.php rename to src/Resolvers/Casts/InputNormalizerCastResolver.php index b3e648a..212e9dc 100644 --- a/src/Resolvers/Casts/NormalizerCastResolver.php +++ b/src/Resolvers/Casts/InputNormalizerCastResolver.php @@ -2,11 +2,9 @@ namespace Astral\Serialize\Resolvers\Casts; -use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Config\ConfigManager; -use Astral\Serialize\Support\Context\InputValueContext; -class NormalizerCastResolver +class InputNormalizerCastResolver { public function __construct( private readonly ConfigManager $configManager @@ -16,10 +14,10 @@ public function __construct( public function resolve(mixed $values): mixed { - foreach ($this->configManager->getNormalizerCasts() as $cast) { + foreach ($this->configManager->getInputNormalizerCasts() as $cast) { $values = $cast->resolve($values); } return $values; } -} \ No newline at end of file +} diff --git a/src/Resolvers/Casts/OutNormalizerCastResolver.php b/src/Resolvers/Casts/OutNormalizerCastResolver.php new file mode 100644 index 0000000..4bcb132 --- /dev/null +++ b/src/Resolvers/Casts/OutNormalizerCastResolver.php @@ -0,0 +1,23 @@ +configManager->getOutNormalizerCasts() as $cast) { + $values = $cast->resolve($values); + } + + return $values; + } +} diff --git a/src/Resolvers/OutputResolver.php b/src/Resolvers/OutputResolver.php index d8df062..8570186 100644 --- a/src/Resolvers/OutputResolver.php +++ b/src/Resolvers/OutputResolver.php @@ -2,8 +2,8 @@ namespace Astral\Serialize\Resolvers; +use Astral\Serialize\Resolvers\Casts\OutNormalizerCastResolver; use Astral\Serialize\Resolvers\Casts\OutputCastResolver; -use Astral\Serialize\Serialize; use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Collections\GroupDataCollection; use Astral\Serialize\Support\Context\ChoosePropertyContext; @@ -17,6 +17,7 @@ public function __construct( protected readonly ReflectionClassInstanceManager $reflectionClassInstanceManager, private readonly OutputCastResolver $outValueCastResolver, protected readonly GroupResolver $groupResolver, + private readonly OutNormalizerCastResolver $normalizerCastResolver, ) { } @@ -52,7 +53,7 @@ classInstance: $object, ); foreach ($matchData['names'] as $name) { - $toArray[$name] = $resolvedValue instanceof Serialize ? $resolvedValue->toArray() : $resolvedValue; + $toArray[$name] = $this->normalizerCastResolver->resolve($resolvedValue); } } diff --git a/src/Serialize.php b/src/Serialize.php index 11e4f9e..30bfca9 100644 --- a/src/Serialize.php +++ b/src/Serialize.php @@ -84,7 +84,6 @@ public static function faker(): static return $instance; } - public function __debugInfo() { $res = get_object_vars($this); @@ -95,16 +94,16 @@ public function __debugInfo() public function jsonSerialize(): array { - $baseResponses = Config::get('response',[]); + $baseResponses = Config::get('response', []); $customerResponses = $this->getContext()?->getResponses() ?? []; - $responses = array_merge($baseResponses, $customerResponses); + $responses = array_merge($baseResponses, $customerResponses); - if($responses){ + if ($responses) { $resultData = []; - foreach ($responses as $field => $item){ - if($item === 'T'){ + foreach ($responses as $field => $item) { + if ($item === 'T') { $resultData[$field] = $this->toArray(); - }else{ + } else { $resultData[$field] = $item['value'] ?? ($item['example'] ?? ''); } } diff --git a/src/SerializeContainer.php b/src/SerializeContainer.php index 8715a71..b96620a 100644 --- a/src/SerializeContainer.php +++ b/src/SerializeContainer.php @@ -7,8 +7,9 @@ use Astral\Serialize\Faker\FakerResolver; use Astral\Serialize\Faker\Rule\FakerDefaultRules; use Astral\Serialize\Resolvers\Casts\DataCollectionCastResolver; +use Astral\Serialize\Resolvers\Casts\InputNormalizerCastResolver; use Astral\Serialize\Resolvers\Casts\InputValueCastResolver; -use Astral\Serialize\Resolvers\Casts\NormalizerCastResolver; +use Astral\Serialize\Resolvers\Casts\OutNormalizerCastResolver; use Astral\Serialize\Resolvers\Casts\OutputCastResolver; use Astral\Serialize\Resolvers\GroupResolver; use Astral\Serialize\Resolvers\InputResolver; @@ -42,7 +43,8 @@ class SerializeContainer protected ?GroupResolver $groupResolver = null; protected ?ReflectionClassInstanceManager $reflectionClassInstanceManager = null; protected ?SerializeInstanceManager $serializeInstanceManager = null; - protected ?NormalizerCastResolver $normalizerCastResolver = null; + protected ?InputNormalizerCastResolver $inputNormalizerCastResolver = null; + protected ?OutNormalizerCastResolver $outNormalizerCastResolver = null; protected ?DataCollectionCastResolver $attributePropertyResolver = null; protected ?InputResolver $propertyInputValueResolver = null; protected ?OutputResolver $propertyToArrayResolver = null; @@ -121,9 +123,14 @@ public function propertyInputValueResolver(): InputResolver ); } - public function normalizerCastResolver(): NormalizerCastResolver + public function inputNormalizerCastResolver(): InputNormalizerCastResolver { - return $this->normalizerCastResolver ??= new NormalizerCastResolver(ConfigManager::getInstance()); + return $this->inputNormalizerCastResolver ??= new InputNormalizerCastResolver(ConfigManager::getInstance()); + } + + public function outNormalizerCastResolver(): OutNormalizerCastResolver + { + return $this->outNormalizerCastResolver ??= new OutNormalizerCastResolver(ConfigManager::getInstance()); } public function inputValueCastResolver(): InputValueCastResolver @@ -147,6 +154,7 @@ public function propertyToArrayResolver(): OutputResolver reflectionClassInstanceManager:$this->reflectionClassInstanceManager(), outValueCastResolver:$this->outValueCastResolver(), groupResolver: $this->groupResolver(), + normalizerCastResolver:$this->outNormalizerCastResolver(), ); } diff --git a/src/Support/Config/ConfigManager.php b/src/Support/Config/ConfigManager.php index 20db6a3..3c002ea 100644 --- a/src/Support/Config/ConfigManager.php +++ b/src/Support/Config/ConfigManager.php @@ -8,8 +8,9 @@ use Astral\Serialize\Casts\InputValue\InputValueEnumCast; use Astral\Serialize\Casts\InputValue\InputValueNullCast; use Astral\Serialize\Casts\Normalizer\ArrayNormalizerCast; +use Astral\Serialize\Casts\Normalizer\DateTimeNormalizerCast; use Astral\Serialize\Casts\Normalizer\JsonNormalizerCast; -use Astral\Serialize\Casts\OutValue\OutArrayChildCast; +use Astral\Serialize\Casts\Normalizer\ObjectNormalizerCast; use Astral\Serialize\Casts\OutValue\OutValueEnumCast; use Astral\Serialize\Casts\OutValue\OutValueGetterCast; use Astral\Serialize\Contracts\Attribute\DataCollectionCastInterface; @@ -17,8 +18,9 @@ use Astral\Serialize\Contracts\Attribute\OutValueCastInterface; use Astral\Serialize\Contracts\Normalizer\NormalizerCastInterface; use Astral\Serialize\Enums\CacheDriverEnum; -use Astral\Serialize\Exceptions\NotFoundAttributePropertyResolver; +use Astral\Serialize\Enums\ConfigCastEnum; use Astral\Serialize\Support\Caching\MemoryCache; +use RuntimeException; class ConfigManager { @@ -38,88 +40,71 @@ class ConfigManager /** @var (OutValueCastInterface|string)[] $outputValueCasts */ private array $outputValueCasts = [ -// OutArrayChildCast::class, OutValueEnumCast::class, OutValueGetterCast::class, ]; - /** @var (NormalizerCastInterface|string)[] $normalizerCasts */ - private array $normalizerCasts = [ + /** @var (NormalizerCastInterface|string)[] $inputNormalizerCasts */ + private array $inputNormalizerCasts = [ // JsonNormalizerCast::class, ArrayNormalizerCast::class, ]; + /** @var (NormalizerCastInterface|string)[] $inputNormalizerCasts */ + private array $outNormalizerCasts = [ + DateTimeNormalizerCast::class, + ArrayNormalizerCast::class, + ObjectNormalizerCast::class + ]; + /** @var CacheDriverEnum|class-string $cacheDriver */ private string|CacheDriverEnum $cacheDriver = MemoryCache::class; - public function __construct() - { - foreach ($this->inputValueCasts as $key => $cast) { - $this->inputValueCasts[$key] = new $cast(); - } - - foreach ($this->outputValueCasts as $key => $cast) { - $this->outputValueCasts[$key] = new $cast(); - } - - foreach ($this->normalizerCasts as $key => $cast) { - $this->normalizerCasts[$key] = new $cast(); - } - } - public static function getInstance(): ConfigManager { return self::$instance ??= new self(); } - /** - * @throws NotFoundAttributePropertyResolver - */ - public function addNormalizerCasts(NormalizerCastInterface|string $resolverClass): static + public function __construct() { - if (is_string($resolverClass) && !is_subclass_of($resolverClass, NormalizerCastInterface::class)) { - throw new NotFoundAttributePropertyResolver('Resolver class must be an instance of NormalizerCastInterface'); - } - $this->attributePropertyResolver[] = (is_string($resolverClass) ? new $resolverClass() : $resolverClass); - - return $this; + $this->instantiateArrayProperties(ConfigCastEnum::getValues()); } /** - * @throws NotFoundAttributePropertyResolver + * @param array $propertyNames + * @return void */ - public function addAttributePropertyResolver(DataCollectionCastInterface|string $resolverClass): static + private function instantiateArrayProperties(array $propertyNames): void { - if (is_string($resolverClass) && !is_subclass_of($resolverClass, DataCollectionCastInterface::class)) { - throw new NotFoundAttributePropertyResolver('Resolver class must be an instance of DataCollectionCastInterface'); + foreach ($propertyNames as $property) { + if (!property_exists($this, $property)) { + throw new RuntimeException("Property $property does not exist"); + } + + $this->$property = array_map( + fn ($class) => is_string($class) ? new $class() : $class, + $this->$property + ); } - $this->attributePropertyResolver[] = (is_string($resolverClass) ? new $resolverClass() : $resolverClass); - - return $this; } /** - * @throws NotFoundAttributePropertyResolver + * @param object|string $castClass + * @param ConfigCastEnum $castEnum + * @return static + * @throws RuntimeException */ - public function addOutputValueCasts(OutValueCastInterface|string $castClass): static + private function addCast(object|string $castClass, ConfigCastEnum $castEnum): static { - if (is_string($castClass) && !is_subclass_of($castClass, OutValueCastInterface::class)) { - throw new NotFoundAttributePropertyResolver('Resolver class must be an instance of OutValueCastInterface'); + if (is_string($castClass) && !is_subclass_of($castClass, $castEnum->getCastInterface())) { + throw new RuntimeException("Cast class must be an instance of {$castEnum->getCastInterface()}"); } - $this->outputValueCasts[] = (is_string($castClass) ? new $castClass() : $castClass); - - return $this; - } - /** - * @throws NotFoundAttributePropertyResolver - */ - public function addInputValueCasts(InputValueCastInterface|string $castClass): static - { - if (is_string($castClass) && !is_subclass_of($castClass, InputValueCastInterface::class)) { - throw new NotFoundAttributePropertyResolver('Resolver class must be an instance of InputValueCastInterface'); + if (!property_exists($this, $castEnum->value)) { + throw new RuntimeException("Property {$castEnum->value} does not exist"); } - $this->inputValueCasts[] = (is_string($castClass) ? new $castClass() : $castClass); + + $this->{$castEnum->value}[] = is_string($castClass) ? new $castClass() : $castClass; return $this; } @@ -139,9 +124,14 @@ public function getOutValueCasts(): array return $this->outputValueCasts; } - public function getNormalizerCasts(): array + public function getInputNormalizerCasts(): array + { + return $this->inputNormalizerCasts; + } + + public function getOutNormalizerCasts(): array { - return $this->normalizerCasts; + return $this->outNormalizerCasts; } public function getCacheDriver(): string diff --git a/src/Support/Context/SerializeContext.php b/src/Support/Context/SerializeContext.php index b601405..c376bb7 100644 --- a/src/Support/Context/SerializeContext.php +++ b/src/Support/Context/SerializeContext.php @@ -5,7 +5,7 @@ use Astral\Serialize\Exceptions\NotFoundGroupException; use Astral\Serialize\Faker\FakerResolver; use Astral\Serialize\Resolvers\Casts\DataCollectionCastResolver; -use Astral\Serialize\Resolvers\Casts\NormalizerCastResolver; +use Astral\Serialize\Resolvers\Casts\InputNormalizerCastResolver; use Astral\Serialize\Resolvers\GroupResolver; use Astral\Serialize\Resolvers\InputResolver; use Astral\Serialize\Resolvers\OutputResolver; @@ -25,7 +25,7 @@ */ class SerializeContext { - private array $groups = []; + private array $groups = []; private array $responses = []; public function __construct( @@ -39,18 +39,18 @@ public function __construct( private readonly ConstructDataCollectionManager $constructDataCollectionManager, private readonly InputResolver $propertyInputValueResolver, private readonly OutputResolver $propertyToArrayResolver, + private readonly InputNormalizerCastResolver $inputNormalizerCastResolver, private readonly FakerResolver $fakerResolver, - private readonly NormalizerCastResolver $normalizerCastResolver, ) { } - public function setCode(string|int $code, $description ='' , $field = 'code'): void + public function setCode(string|int $code, $description = '', $field = 'code'): void { $this->responses[$field] = ['description' => $description,'value' => $code]; } - public function setMessage(string $message, $description ='' , $field = 'message'): void + public function setMessage(string $message, $description = '', $field = 'message'): void { $this->responses[$field] = ['description' => $description,'value' => $message]; } @@ -213,9 +213,9 @@ public function from(mixed ...$payload): object { $payloads = []; foreach ($payload as $field => $itemPayload) { - $itemPayload = $this->normalizerCastResolver->resolve($itemPayload); - $values = is_numeric($field) && is_array($itemPayload) ? $itemPayload : [$field => $itemPayload]; - $payloads = [...$payloads, ...$values]; + $itemPayload = $this->inputNormalizerCastResolver->resolve($itemPayload); + $values = is_numeric($field) && is_array($itemPayload) ? $itemPayload : [$field => $itemPayload]; + $payloads = [...$payloads, ...$values]; } $this->chooseSerializeContext->setGroups($this->getGroups()); @@ -245,5 +245,4 @@ public function toArray(object $object): array $this->chooseSerializeContext->setGroups($this->getGroups()); return $this->propertyToArrayResolver->resolve($this->chooseSerializeContext, $this->getGroupCollection(), $object); } - } diff --git a/src/Support/Factories/ContextFactory.php b/src/Support/Factories/ContextFactory.php index 4661f00..f462f0f 100644 --- a/src/Support/Factories/ContextFactory.php +++ b/src/Support/Factories/ContextFactory.php @@ -19,16 +19,16 @@ public static function build(string $className): SerializeContext { return (new SerializeContext( serializeClassName: $className, - chooseSerializeContext:new ChooseSerializeContext($className), + chooseSerializeContext: new ChooseSerializeContext($className), cache: CacheFactory::build(), reflectionClassInstanceManager: SerializeContainer::get()->reflectionClassInstanceManager(), groupResolver: SerializeContainer::get()->groupResolver(), dataCollectionCastResolver: SerializeContainer::get()->attributePropertyResolver(), constructDataCollectionManager: SerializeContainer::get()->constructDataCollectionManager(), propertyInputValueResolver: SerializeContainer::get()->propertyInputValueResolver(), - propertyToArrayResolver: SerializeContainer::get()->propertyToArrayResolver(), + propertyToArrayResolver: SerializeContainer::get()->propertyToArrayResolver(), + inputNormalizerCastResolver: SerializeContainer::get()->inputNormalizerCastResolver(), fakerResolver: SerializeContainer::get()->fakerResolver(), - normalizerCastResolver: SerializeContainer::get()->normalizerCastResolver(), )); } } diff --git a/tests/Openapi/BaseGroupOpenApiTest.php b/tests/Openapi/BaseGroupOpenApiTest.php index e83f48e..3acba1b 100644 --- a/tests/Openapi/BaseGroupOpenApiTest.php +++ b/tests/Openapi/BaseGroupOpenApiTest.php @@ -8,7 +8,6 @@ class TestBaseGroupRequest extends Serialize { - public int $input_group_0; #[\Astral\Serialize\Annotations\Groups('group_1')] @@ -17,8 +16,8 @@ class TestBaseGroupRequest extends Serialize #[\Astral\Serialize\Annotations\Groups('group_1')] public string $input_group_1_22; - #[\Astral\Serialize\Annotations\Groups('group_1','group_2')] - #[\Astral\Serialize\Annotations\DataCollection\InputName('input_change_group_1_33_2_11',['group_2'])] + #[\Astral\Serialize\Annotations\Groups('group_1', 'group_2')] + #[\Astral\Serialize\Annotations\DataCollection\InputName('input_change_group_1_33_2_11', ['group_2'])] public string $input_group_1_33_2_11; #[\Astral\Serialize\Annotations\Groups('group_2')] @@ -39,7 +38,7 @@ class TestBaseGroupResponse extends Serialize #[\Astral\Serialize\Annotations\Groups('group_1')] public string $out_group_1_22; - #[\Astral\Serialize\Annotations\Groups('group_1','group_2')] + #[\Astral\Serialize\Annotations\Groups('group_1', 'group_2')] public string $out_group_1_33_2_11; #[\Astral\Serialize\Annotations\Groups('group_2')] diff --git a/tests/Openapi/EnumOpenApiTest.php b/tests/Openapi/EnumOpenApiTest.php index ff8660e..72b21f7 100644 --- a/tests/Openapi/EnumOpenApiTest.php +++ b/tests/Openapi/EnumOpenApiTest.php @@ -69,24 +69,24 @@ public function one(OpenapiEnumRequest $request): void 'test_one_of_enum', ]) ->and($schema['properties']['test_enum'])->toMatchArray([ - 'type' => 'string', + 'type' => 'string', 'description' => 'optional values:ENUM_1、ENUM_2', - 'example' => '', + 'example' => '', ]) ->and($schema['properties']['test_string_enum'])->toMatchArray([ - 'type' => 'string', + 'type' => 'string', 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', - 'example' => '', + 'example' => '', ]) ->and($schema['properties']['test_string_2_enum'])->toMatchArray([ - 'type' => 'string', + 'type' => 'string', 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', - 'example' => '', + 'example' => '', ]) ->and($schema['properties']['test_one_of_enum'])->toMatchArray([ - 'type' => 'oneOf', + 'type' => 'oneOf', 'description' => 'optional values:ENUM_1、ENUM_2、ENUM_3、ENUM_4', - 'example' => '', + 'example' => '', ]) ->and($schema['properties']['test_one_of_enum']['oneOf'])->toBeArray()->toHaveCount(2) ->and($schema['properties']['test_one_of_enum']['oneOf'][0])->toMatchArray(['type' => 'string']) diff --git a/tests/Openapi/OpenApiTest.php b/tests/Openapi/OpenApiTest.php index 70f491a..8db1fc2 100644 --- a/tests/Openapi/OpenApiTest.php +++ b/tests/Openapi/OpenApiTest.php @@ -59,37 +59,30 @@ public function one(TestOpenApiRequest $request): TestOpenApiResponse // 顶层结构断言 expect($openApi->openapi)->toBe('3.1.1') ->and($openApi->info->version)->toBe('1.0.0'); -// ->and($openApi->tags[0]->name)->toBe('接口测试'); + // ->and($openApi->tags[0]->name)->toBe('接口测试'); - // 路径是否存在 $paths = $openApi->paths; expect($paths)->toHaveKey('/test/one-action'); - // 方法与标签断言 $post = $paths['/test/one-action']['post']; expect($post->summary)->toBe('测试方法一') ->and($post->tags)->toContain('接口测试'); - // 请求体断言 $requestBody = $post->requestBody; expect($requestBody['required'])->toBeTrue(); $schema = $requestBody['content']['application/json']['schema']; - // 请求字段存在 expect($schema['properties'])->toHaveKeys(['name', 'id', 'any_array']); - // id 字段是 oneOf 并包含 string, integer, number $idOneOf = $schema['properties']['id']['oneOf']; $types = array_map(static fn ($item) => $item['type'], $idOneOf); expect($types)->toMatchArray(['string', 'integer', 'number']); - // any_array 是 oneOf 并包含至少一个 array 类型 $anyArray = $schema['properties']['any_array']; expect($anyArray['type'])->toBe('oneOf') ->and($anyArray['oneOf'])->toBeArray() ->and($anyArray['oneOf'][0]['type'])->toBe('array'); - // 响应体 200 是否定义成功 $response200 = $post->responses[200]; expect($response200['description'])->toBe('成功'); diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index 9feac3a..0f2e76b 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -31,13 +31,13 @@ class NormalizerClass extends Serialize it('test normalizer Serialize class', function () { - $normalizerOne = new NormalizerOne(); + $normalizerOne = new NormalizerOne(); $normalizerOne->name_one = 'one name'; - $normalizerOne->id_one = 1; + $normalizerOne->id_one = 1; - $normalizerTwo = new NormalizerTwo(); + $normalizerTwo = new NormalizerTwo(); $normalizerTwo->name_two = 'two name'; - $normalizerTwo->id_two = 2; + $normalizerTwo->id_two = 2; $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); @@ -50,21 +50,21 @@ class NormalizerClass extends Serialize ->and($res->three)->toBeArray() ->and($res->three)->toMatchArray([ 'name_one' => 'one name', - 'id_one' => 1 + 'id_one' => 1 ]); -// + // }); it('test json_encode Serialize class', function () { - $normalizerOne = new NormalizerOne(); + $normalizerOne = new NormalizerOne(); $normalizerOne->name_one = 'one name'; - $normalizerOne->id_one = 1; + $normalizerOne->id_one = 1; - $normalizerTwo = new NormalizerTwo(); + $normalizerTwo = new NormalizerTwo(); $normalizerTwo->name_two = 'two name'; - $normalizerTwo->id_two = 2; + $normalizerTwo->id_two = 2; $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); @@ -86,4 +86,4 @@ class NormalizerClass extends Serialize $resJson = $res->toJsonString(); expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); -}); \ No newline at end of file +}); diff --git a/tests/Serialize/ToArray/OutDateFormatToArraySerializeTest.php b/tests/Serialize/ToArray/OutDateFormatToArraySerializeTest.php index eb91a6a..36bec39 100644 --- a/tests/Serialize/ToArray/OutDateFormatToArraySerializeTest.php +++ b/tests/Serialize/ToArray/OutDateFormatToArraySerializeTest.php @@ -36,7 +36,7 @@ class TestOutDateFormat extends Serialize $object->date_3 = new DateTime('2023-10-05 14:30:00'); // DateTime object $object->date_4 = Carbon::create(2023, 10, 5); // Carbon object $object->date_5 = new DateTime('2023-10-05'); // DateTime object without annotation - $object->date_6 = Carbon::create(2023, 10, 5); // Carbon object without annotation + $object->date_6 = Carbon::create(2023, 10, 6); // Carbon object without annotation // Serialize the object to an array $result = $object->toArray(); @@ -46,6 +46,6 @@ class TestOutDateFormat extends Serialize ->and($result['date_2'])->toBe('2023-10-05 00:00:00') // Default format Y-m-d H:i:s ->and($result['date_3'])->toBe('05/10/2023 14:30:00') // Formatted with 'd/m/Y H:i:s' ->and($result['date_4'])->toBe('05/10/2023') // Formatted with 'd/m/Y' - ->and($result['date_5'])->toBeInstanceOf(DateTime::class) // Default DateTime format - ->and($result['date_6'])->toBeInstanceOf(Carbon::class); // Default Carbon format + ->and($result['date_5'])->toBe('2023-10-05 00:00:00') // Default DateTime format Y-m-d H:i:s + ->and($result['date_6'])->toBe('2023-10-06 00:00:00'); // Default Carbon format Y-m-d H:i:s }); From 1ac0fb3069177157b7b0165060046ae119faf3ed Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Mon, 1 Sep 2025 11:51:38 +0800 Subject: [PATCH 10/11] add cast tests --- src/Annotations/Input/InputDateFormat.php | 4 --- src/Annotations/Output/OutputDateFormat.php | 4 --- .../Normalizer/DateTimeNormalizerCast.php | 5 ++- src/Casts/Normalizer/JsonNormalizerCast.php | 2 +- src/OpenApi/Collections/OpenApiCollection.php | 4 +-- src/Support/Config/ConfigManager.php | 2 +- tests/Serialize/Config/AddCastTest.php | 36 +++++++++++++++++++ .../From/NormalizerFromSerializeTest.php | 1 - 8 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 tests/Serialize/Config/AddCastTest.php diff --git a/src/Annotations/Input/InputDateFormat.php b/src/Annotations/Input/InputDateFormat.php index c46a284..41e4585 100755 --- a/src/Annotations/Input/InputDateFormat.php +++ b/src/Annotations/Input/InputDateFormat.php @@ -8,7 +8,6 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\InputValueContext; use Attribute; -use DateInvalidTimeZoneException; use DateTime; use DateTimeInterface; use DateTimeZone; @@ -29,9 +28,6 @@ public function match(mixed $value, DataCollection $collection, InputValueContex return is_string($value) || is_numeric($value); } - /** - * @throws DateInvalidTimeZoneException - */ public function resolve(mixed $value, DataCollection $collection, InputValueContext $context): string|DateTime { diff --git a/src/Annotations/Output/OutputDateFormat.php b/src/Annotations/Output/OutputDateFormat.php index f343e36..e2682f2 100755 --- a/src/Annotations/Output/OutputDateFormat.php +++ b/src/Annotations/Output/OutputDateFormat.php @@ -8,7 +8,6 @@ use Astral\Serialize\Support\Collections\DataCollection; use Astral\Serialize\Support\Context\OutContext; use Attribute; -use DateInvalidTimeZoneException; use DateTime; use DateTimeInterface; use DateTimeZone; @@ -33,9 +32,6 @@ public function resolve(mixed $value, DataCollection $collection, OutContext $co return $this->formatValue($value); } - /** - * @throws DateInvalidTimeZoneException - */ private function formatValue(mixed $value): ?string { $timezone = $this->timezone ? new DateTimeZone($this->timezone) : null; diff --git a/src/Casts/Normalizer/DateTimeNormalizerCast.php b/src/Casts/Normalizer/DateTimeNormalizerCast.php index 86bb566..91484af 100644 --- a/src/Casts/Normalizer/DateTimeNormalizerCast.php +++ b/src/Casts/Normalizer/DateTimeNormalizerCast.php @@ -4,7 +4,7 @@ use Astral\Serialize\Contracts\Normalizer\NormalizerCastInterface; use DateTimeInterface; -use JsonException; +use Throwable; class DateTimeNormalizerCast implements NormalizerCastInterface { @@ -18,8 +18,7 @@ public function resolve(mixed $values): mixed if ($this->match($values)) { try { return $values->format('Y-m-d H:i:s'); - } catch (JsonException $e) { - return ''; + } catch (Throwable $e) { } } diff --git a/src/Casts/Normalizer/JsonNormalizerCast.php b/src/Casts/Normalizer/JsonNormalizerCast.php index 4a6bd87..a4f50f3 100644 --- a/src/Casts/Normalizer/JsonNormalizerCast.php +++ b/src/Casts/Normalizer/JsonNormalizerCast.php @@ -12,7 +12,7 @@ public function match(mixed $values): bool return is_string($values); } - public function resolve(mixed $values): array + public function resolve(mixed $values): mixed { if ($this->match($values)) { try { diff --git a/src/OpenApi/Collections/OpenApiCollection.php b/src/OpenApi/Collections/OpenApiCollection.php index 5bf428f..02d33f3 100644 --- a/src/OpenApi/Collections/OpenApiCollection.php +++ b/src/OpenApi/Collections/OpenApiCollection.php @@ -31,8 +31,8 @@ public function __construct( public string $methodName, public reflectionMethod $reflectionMethod, public Tag $tag, - public Summary $summary, - public Route $route, + public Summary|null $summary, + public Route|null $route, public Headers|null $headers, public array $attributes, public RequestBody|null $requestBody, diff --git a/src/Support/Config/ConfigManager.php b/src/Support/Config/ConfigManager.php index 3c002ea..310d26f 100644 --- a/src/Support/Config/ConfigManager.php +++ b/src/Support/Config/ConfigManager.php @@ -94,7 +94,7 @@ private function instantiateArrayProperties(array $propertyNames): void * @return static * @throws RuntimeException */ - private function addCast(object|string $castClass, ConfigCastEnum $castEnum): static + public function addCast(object|string $castClass, ConfigCastEnum $castEnum): static { if (is_string($castClass) && !is_subclass_of($castClass, $castEnum->getCastInterface())) { throw new RuntimeException("Cast class must be an instance of {$castEnum->getCastInterface()}"); diff --git a/tests/Serialize/Config/AddCastTest.php b/tests/Serialize/Config/AddCastTest.php new file mode 100644 index 0000000..fb34ce3 --- /dev/null +++ b/tests/Serialize/Config/AddCastTest.php @@ -0,0 +1,36 @@ +toArray())->toHaveCount(2) + ->and($res->toArray()['name_three'])->toBeNull() + ->and($res->toArray()['id_three'])->toBeNull(); + + ConfigManager::getInstance()->addCast(JsonNormalizerCast::class, ConfigCastEnum::INPUT_NORMALIZER); + $res = AddCastTestClass::from('{"name_three_other":"1223","id_three":3}'); + expect($res->toArray())->toHaveCount(2) + ->and($res->toArray()['name_three'])->toBe('1223') + ->and($res->toArray()['id_three'])->toBe(3); + +}); diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index 0f2e76b..2ccb6e2 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -80,7 +80,6 @@ class NormalizerClass extends Serialize expect($resJson)->toBe('{"code":-1,"message":"233","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); $resJson = $res->withoutResponseToJsonString(); - var_dump($resJson); expect($resJson)->toBe('{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}'); $resJson = $res->toJsonString(); From c4dcf98360b047422ebe5602baf5da17db865382 Mon Sep 17 00:00:00 2001 From: "350375092@qq.com" <350375092@qq.com> Date: Tue, 2 Sep 2025 09:54:51 +0800 Subject: [PATCH 11/11] add doc --- .openapi.php | 4 +- docs/en/SUMMARY.md | 1 + docs/en/mapper/out.md | 109 +++++++++++++++++ docs/en/openapi/config.md | 4 +- docs/zh/SUMMARY.md | 1 + docs/zh/mapper/out.md | 110 ++++++++++++++++++ docs/zh/openapi/config.md | 2 +- tests/Serialize/Config/AddCastTest.php | 2 - .../From/NormalizerFromSerializeTest.php | 2 +- 9 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 docs/en/mapper/out.md create mode 100644 docs/zh/mapper/out.md diff --git a/.openapi.php b/.openapi.php index 302fb95..49bd55b 100644 --- a/.openapi.php +++ b/.openapi.php @@ -57,8 +57,8 @@ * */ 'response' => [ - 'code' => ['description' =>'状态码', 'example'=> 200], - 'message' => ['description' =>'返回信息', 'example'=> '操作成功'], + 'code' => ['description' =>'code', 'example'=> 200], + 'message' => ['description' =>'message', 'example'=> 'success'], 'data' => 'T', ] ]; diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 6710302..4dd73b3 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -10,6 +10,7 @@ * [Enum Mapping](mapper/enum-mapper.md) * [Array Object Mapping](mapper/array-mapper.md) * [Union Type Mapping](mapper/union-mapper.md) +* [Output Format](mapper/out.md) ## Annotation Usage diff --git a/docs/en/mapper/out.md b/docs/en/mapper/out.md new file mode 100644 index 0000000..2e4c2cc --- /dev/null +++ b/docs/en/mapper/out.md @@ -0,0 +1,109 @@ +## Output Format + +### Creating the Serialize Class + +```php +use Astral\Serialize\Serialize; +use DateTime; + +class UserLoginLog extends Serialize { + public string $remark, + public DateTime $create_time; +} + +class User extends Serialize { + public string $name, + public int $age, + public UserLoginLog $login_log +} + +// Create an object +$user = User::from([ + 'name' => 'Jon', + 'age' => 30 +], login_log: new UserLoginLog(remark:'Test Data',create_time: DateTime::createFromFormat('Y-m-d','2008-09-01'))); +```` + +### Outputting the Object + +```php +// $user is an object by default +echo $user->name; // Output: Jon +echo $user->age; // Output: 30 +echo $user->login_log->remark; // Output 'Test Data' +echo $user->login_log->create_time; // Output DateTime Object + +``` + +### Outputting an Array + +```php +// Convert to an array +$vols = $user->toArray(); +echo $vols['name']; // Output: Jon +echo $vols['age']; // Output: 30 +echo $vols['login_log']['remark']; // Output 'Test Data' +echo $vols['login_log']['create_time']; // Output 2008-09-01 00:00:00 +// Content of $vols: +// [ +// 'name' => 'Jon', +// 'age' => 30, +// 'login_log' => [ +// [ +// 'remark' => 'Test Data', +// 'create_time' => '2008-09-01 00:00:00' +// ] +// ] +// ] +``` + +### Outputting a JSON String + +1. The `Serialize` class implements `JsonSerializable` by default. Similar to a `Laravel` `Controller` you can directly return the object, and the framework will output the `JSON` information correctly +2. By default, the JSON output from `Serialize` includes `data` `code` and `message` f you need to [replace/modify/add] these, please refer to the configuration information [Response Data Structure Definition](../openapi/config.md) + +#### Outputting JSON Information + +- You can use the API `toJsonString` +- Alternatively, you can directly use `json_decode` + +```php +echo $user->toJsonString(); +echo json_decode($user); +// Both outputs are the same +// {"code":200,"message":"success","data":{"name":"Jon","age":30,"login_log":{"remark":"Test Data","create_time":"2008-09-01 00:00:00"}} +``` + +#### Setting Output Code/Message + +```php +$user->setCode(500); +$user->setMessage('Operation failed'); +echo json_decode($user); +// Output +// {"code":500,"message":"Operation failed","data":{"name":"Jon","age":30,"login_log":{"remark":"Test Data","create_time":"2008-09-01 00:00:00"}} +``` + +#### Setting Custom JSON Outer Layer + +`withResponses` can temporarily add or modify custom return information. To add global return information, you can configure it in the [Response Data Structure Definition](../openapi/config.md) + +```php +$user->withResponses([ + "code"=> ["description"=>"Return Code", "value"=>401], + "message"=> ["description"=>"Return Message", "value"=>"Operation successful"], + "error"=> ["description"=>"Return Error", "value"=>0], +]); +// Output +// {"code":401,"message":"Operation successful","error":0,"data":{"name":"Jon","age":30,"login_log":{"remark":"Test Data","create_time":"2008-09-01 00:00:00"}} +``` + +#### Outputting JSON Without Outer Layer Information + +Use`withoutResponseToJsonString` to return JSON data containing only the object’s properties. + +```php +$user->withoutResponseToJsonString(); +// Output +// {"name":"Jon","age":30,"login_log":{"remark":"Test Data","create_time":"2008-09-01 00:00:00"} +``` \ No newline at end of file diff --git a/docs/en/openapi/config.md b/docs/en/openapi/config.md index c5f2b94..093493a 100644 --- a/docs/en/openapi/config.md +++ b/docs/en/openapi/config.md @@ -68,8 +68,8 @@ return [ * */ 'response' => [ - 'code' => ['description' =>'状态码', 'example'=> 200], - 'message' => ['description' =>'返回信息', 'example'=> '操作成功'], + 'code' => ['description' =>'code', 'example'=> 200], + 'message' => ['description' =>'message', 'example'=> 'success'], 'data' => 'T', ] ]; diff --git a/docs/zh/SUMMARY.md b/docs/zh/SUMMARY.md index 3d2b495..0beb677 100644 --- a/docs/zh/SUMMARY.md +++ b/docs/zh/SUMMARY.md @@ -10,6 +10,7 @@ * [枚举转换](mapper/enum-mapper.md) * [数组对象转换](mapper/array-mapper.md) * [联合类型转换](mapper/union-mapper.md) +* [输出格式](mapper/out.md) ## 注解类使用 diff --git a/docs/zh/mapper/out.md b/docs/zh/mapper/out.md new file mode 100644 index 0000000..e8fcf15 --- /dev/null +++ b/docs/zh/mapper/out.md @@ -0,0 +1,110 @@ +## 输出格式 + +### 创建Serialize类 + +```php +use Astral\Serialize\Serialize; +use DateTime; + +class UserLoginLog extends Serialize { + public string $remark, + public DateTime $create_time; +} + +class User extends Serialize { + public string $name, + public int $age, + public UserLoginLog $login_log +} + +// 创建对象 +$user = User::from([ + 'name' => '张三', + 'age' => 30 +], login_log: new UserLoginLog(remark:'测试数据',create_time: DateTime::createFromFormat('Y-m-d','2008-09-01'))); +```` + +### 输出对象 + +```php +// $user默认就是一个对象 +echo $user->name; // 输出: 张三 +echo $user->age; // 输出: 30 +echo $user->login_log->remark; // 输出 '测试数据' +echo $user->login_log->create_time; // 输出 DateTime对象 + +``` + +### 输出数组 + +```php +// 转换成数组 +$vols = $user->toArray(); +echo $vols['name']; // 输出: 张三 +echo $vols['age']; // 输出: 30 +echo $vols['login_log']['remark']; // 输出 '测试数据' +echo $vols['login_log']['create_time']; // 输出 2008-09-01 00:00:00 +// $vols 的内容: +// [ +// 'name' => '张三', +// 'age' => 30, +// 'login_log' => [ +// [ +// 'remark' => '测试数据', +// 'create_time' => '2008-09-01 00:00:00' +// ] +// ] +// ] +``` + +### 输出数组json字符串 + +1. `Serialize` 默认实现了 `JsonSerializable` 类似`Laravel`的`Controller` 可以直接返回对象,框架会正常输出`json信息` +2. `Serialize` 默认json 增加 `data` `code` `message` 如果需要`[替换/修改/增加]` +请查看配置信息 [响应数据结构定义](../openapi/config.md) + +#### 输出json信息 + +- 可以使用 api `toJsonString` +- 也可以直接使用 `json_decode` + +```php +echo $user->toJsonString(); +echo json_decode($user); +// 输出内容相同 +// {"code":200,"message":"success","data":{"name":"张三","age":30,"login_log":{"remark":"测试数据","create_time":"2008-09-01 00:00:00"}} +``` + +#### 设置输出code/message + +```php +$user->setCode(500); +$user->setMessage('操作失败'); +echo json_decode($user); +// 输出内容 +// {"code":500,"message":"操作失败","data":{"name":"张三","age":30,"login_log":{"remark":"测试数据","create_time":"2008-09-01 00:00:00"}} +``` + +#### 设置自定义json外层 + +`withResponses` 可以临时增加修改自定义返回信息,全局增加返回信息可以在 [响应数据结构定义](../openapi/config.md)中配置 + +```php +$user->withResponses([ + "code"=> ["description"=>"返回code", "value"=>401], + "message"=> ["description"=>"返回信息", "value"=>"操作成功"], + "error"=> ["description"=>"返回error", "value"=>0], +]); +// 输出内容 +// {"code":401,"message":"操作成功","error":0,"data":{"name":"张三","age":30,"login_log":{"remark":"测试数据","create_time":"2008-09-01 00:00:00"}} +``` + +#### 输出不包含外层信息的Json + +使用`withoutResponseToJsonString` 可以返回只有对象属性的json数据 + +```php +$user->withoutResponseToJsonString(); +// 输出内容 +// {"name":"张三","age":30,"login_log":{"remark":"测试数据","create_time":"2008-09-01 00:00:00"} +``` \ No newline at end of file diff --git a/docs/zh/openapi/config.md b/docs/zh/openapi/config.md index 68a099c..259f886 100644 --- a/docs/zh/openapi/config.md +++ b/docs/zh/openapi/config.md @@ -69,7 +69,7 @@ return [ */ 'response' => [ 'code' => ['description' =>'状态码', 'example'=> 200], - 'message' => ['description' =>'返回信息', 'example'=> '操作成功'], + 'message' => ['description' =>'返回信息', 'example'=> 'success'], 'data' => 'T', ] ]; diff --git a/tests/Serialize/Config/AddCastTest.php b/tests/Serialize/Config/AddCastTest.php index fb34ce3..3ace6cf 100644 --- a/tests/Serialize/Config/AddCastTest.php +++ b/tests/Serialize/Config/AddCastTest.php @@ -8,8 +8,6 @@ use Astral\Serialize\Support\Config\ConfigManager; beforeAll(function () { - - class AddCastTestClass extends Serialize { #[InputName('name_three_other')] diff --git a/tests/Serialize/From/NormalizerFromSerializeTest.php b/tests/Serialize/From/NormalizerFromSerializeTest.php index b83d09e..0d8f0b0 100644 --- a/tests/Serialize/From/NormalizerFromSerializeTest.php +++ b/tests/Serialize/From/NormalizerFromSerializeTest.php @@ -69,7 +69,7 @@ class NormalizerClass extends Serialize $res = NormalizerClass::from(one: $normalizerOne, two: $normalizerTwo, three: $normalizerOne); $resJson = json_encode($res); - expect($resJson)->toBe('{"code":200,"message":"\u64cd\u4f5c\u6210\u529f","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); + expect($resJson)->toBe('{"code":200,"message":"success","data":{"one":{"name_one":"one name","id_one":1},"two":{"name_two":"two name","id_2":2},"three":{"name_one":"one name","id_one":1}}}'); $res->setMessage('233'); $resJson = json_encode($res);